"""
Configuration management for the data preprocessing pipeline.

This module handles user input collection for preprocessing configuration,
including layer count specification and method selection for each layer.
"""

import itertools
from typing import List, Dict, Optional
import logging

from .models import ProcessingConfiguration
from .exceptions import ConfigurationError, UserCancellationError
from .error_recovery import ErrorRecoveryManager


class ConfigurationManager:
    """
    Manages user configuration input for preprocessing pipeline setup.
    
    Handles collection of layer count, method selections per layer,
    input validation, and combination count calculation with warnings.
    """
    
    def __init__(self, error_recovery_manager: Optional[ErrorRecoveryManager] = None):
        """
        Initialize the configuration manager.
        
        Args:
            error_recovery_manager: Optional error recovery manager for handling errors
        """
        self.logger = logging.getLogger(__name__)
        self.error_recovery_manager = error_recovery_manager or ErrorRecoveryManager(self.logger)
    
    def get_layer_count(self) -> int:
        """
        Prompt user for number of processing layers with validation.
        
        Validates that input is a positive integer between 1 and 10.
        Re-prompts on invalid input with error messages.
        
        Returns:
            int: Valid layer count between 1 and 10
            
        Requirements: 2.1, 2.2, 2.3
        
        Raises:
            UserCancellationError: If user cancels input
            ConfigurationError: If validation fails after max attempts
        """
        max_attempts = 5
        attempts = 0
        
        while attempts < max_attempts:
            try:
                print("\n=== 层级配置 ===")
                layer_input = input("请输入预处理层数 (1-10): ").strip()
                
                # Check for cancellation
                if layer_input.lower() in ['quit', 'exit', 'cancel']:
                    raise UserCancellationError("Layer count input cancelled by user", "layer_count")
                
                # Validate input
                layer_count = int(layer_input)
                
                # 验证层数范围
                if layer_count < 1 or layer_count > 10:
                    raise ConfigurationError(
                        f"Layer count must be between 1 and 10",
                        parameter="layer_count",
                        valid_range="1-10",
                        provided_value=layer_count
                    )
                
                validated_count = layer_count
                
                print(f"✓ 层数设置为: {validated_count}")
                self.logger.info(f"Layer count configured: {validated_count}")
                return validated_count
                
            except ValueError:
                attempts += 1
                error_msg = f"输入无效: 请输入1到10之间的整数。(尝试 {attempts}/{max_attempts})"
                print(f"错误: {error_msg}")
                self.logger.warning(f"Invalid layer count input: {layer_input}")
                
                if attempts >= max_attempts:
                    raise ConfigurationError(
                        "Maximum input attempts exceeded for layer count",
                        parameter="layer_count",
                        valid_range="1-10",
                        provided_value=layer_input
                    )
                continue
                
            except ConfigurationError as e:
                attempts += 1
                print(f"Error: {e.get_user_message()}")
                self.logger.warning(f"Configuration error: {e}")
                
                if attempts >= max_attempts:
                    raise
                continue
                
            except KeyboardInterrupt:
                raise UserCancellationError("Layer count input interrupted by user", "layer_count")
            
            except Exception as e:
                attempts += 1
                error_msg = f"Unexpected error: {str(e)}"
                print(f"Error: {error_msg}")
                self.logger.error(f"Unexpected error in layer count input: {e}")
                
                if attempts >= max_attempts:
                    raise ConfigurationError(
                        "Failed to get valid layer count after multiple attempts",
                        parameter="layer_count",
                        details=str(e)
                    )
                continue
    
    def get_layer_selections(self, layer_count: int, available_methods: List[int], method_names: Dict[int, str] = None) -> List[List[int]]:
        """
        Collect preprocessing method selections for each layer.
        
        Displays available methods for each layer and allows multiple selections.
        Includes method 0 (no processing) as default option.
        
        Args:
            layer_count (int): Number of layers to configure
            available_methods (List[int]): List of available preprocessing method IDs
            
        Returns:
            List[List[int]]: List of selected method IDs for each layer
            
        Requirements: 3.1, 3.2, 3.3
        
        Raises:
            ConfigurationError: If validation fails
            UserCancellationError: If user cancels input
        """
        try:
            # Validate inputs
            if layer_count < 1 or layer_count > 10:
                raise ConfigurationError(
                    f"Invalid layer count: {layer_count}",
                    parameter="layer_count",
                    valid_range="1-10",
                    provided_value=layer_count
                )
            
            if not available_methods:
                raise ConfigurationError(
                    "No preprocessing methods available",
                    parameter="available_methods"
                )
            
            layer_selections = []
            
            # Ensure method 0 (no processing) is always available
            if 0 not in available_methods:
                available_methods = [0] + sorted(available_methods)
            else:
                available_methods = sorted(available_methods)
            
            print(f"\n=== 方法选择 ===")
            print("可用的预处理方法:")
            for method_id in available_methods:
                if method_names and method_id in method_names:
                    method_name = method_names[method_id]
                    print(f"  {method_id}: {method_name}")
                else:
                    if method_id == 0:
                        print(f"  {method_id}: 无处理 (默认)")
                    else:
                        print(f"  {method_id}: 方法 {method_id}")
            
            # Collect selections for each layer
            for layer_num in range(1, layer_count + 1):
                try:
                    layer_methods = self._get_single_layer_selection(layer_num, available_methods)
                    layer_selections.append(layer_methods)
                except UserCancellationError:
                    raise UserCancellationError(f"Method selection cancelled at layer {layer_num}", "method_selection")
            
            # Validate all selections
            for i, layer_methods in enumerate(layer_selections):
                if not layer_methods:
                    raise ConfigurationError(
                        f"Layer {i+1} has no methods selected",
                        parameter=f"layer_{i+1}_methods"
                    )
                
                for method_id in layer_methods:
                    if method_id not in available_methods:
                        raise ConfigurationError(
                            f"Method {method_id} is not available",
                            parameter=f"layer_{i+1}_method",
                            valid_range=f"Available methods: {available_methods}",
                            provided_value=method_id
                        )
            
            # Calculate and warn about combination count
            self._check_combination_count(layer_selections)
            
            self.logger.info(f"Layer selections configured: {layer_selections}")
            return layer_selections
            
        except (ConfigurationError, UserCancellationError):
            raise
        except Exception as e:
            raise ConfigurationError(
                "Failed to collect layer selections",
                parameter="layer_selections",
                details=str(e)
            )
    
    def _get_single_layer_selection(self, layer_num: int, available_methods: List[int]) -> List[int]:
        """
        Get method selections for a single layer with validation.
        
        Args:
            layer_num (int): Layer number (1-based)
            available_methods (List[int]): Available method IDs
            
        Returns:
            List[int]: Selected method IDs for this layer
            
        Raises:
            ConfigurationError: If validation fails after max attempts
            UserCancellationError: If user cancels input
        """
        max_attempts = 3
        attempts = 0
        
        while attempts < max_attempts:
            try:
                print(f"\n第 {layer_num} 层:")
                print("输入方法ID，用逗号分隔 (例如: 0,1,2) 或直接按回车选择方法0:")
                selection_input = input(f"第 {layer_num} 层方法: ").strip()
                
                # Check for cancellation
                if selection_input.lower() in ['quit', 'exit', 'cancel']:
                    raise UserCancellationError(f"Layer {layer_num} method selection cancelled by user", "method_selection")
                
                # Default to method 0 if empty input
                if not selection_input:
                    selected_methods = [0]
                else:
                    # Parse comma-separated input
                    selected_methods = []
                    for method_str in selection_input.split(','):
                        try:
                            method_id = int(method_str.strip())
                            
                            # Validate method ID exists
                            if method_id not in available_methods:
                                raise ConfigurationError(
                                    f"Method {method_id} is not available",
                                    parameter=f"layer_{layer_num}_method",
                                    valid_range=f"Available methods: {available_methods}",
                                    provided_value=method_id
                                )
                            
                            if method_id not in selected_methods:
                                selected_methods.append(method_id)
                                
                        except ValueError:
                            raise ConfigurationError(
                                f"Invalid method ID: '{method_str.strip()}'",
                                parameter=f"layer_{layer_num}_method",
                                valid_range="Integer method IDs",
                                provided_value=method_str.strip()
                            )
                    
                    if not selected_methods:
                        raise ConfigurationError(
                            "At least one method must be selected",
                            parameter=f"layer_{layer_num}_methods"
                        )
                
                selected_methods.sort()
                print(f"✓ 第 {layer_num} 层方法: {', '.join(map(str, selected_methods))}")
                self.logger.info(f"Layer {layer_num} methods selected: {selected_methods}")
                return selected_methods
                
            except ConfigurationError as e:
                attempts += 1
                print(f"Error: {e.get_user_message()}")
                self.logger.warning(f"Configuration error in layer {layer_num}: {e}")
                
                if attempts >= max_attempts:
                    raise ConfigurationError(
                        f"Failed to get valid method selection for layer {layer_num} after {max_attempts} attempts",
                        parameter=f"layer_{layer_num}_methods",
                        details=str(e)
                    )
                continue
                
            except KeyboardInterrupt:
                raise UserCancellationError(f"Layer {layer_num} method selection interrupted by user", "method_selection")
            
            except Exception as e:
                attempts += 1
                error_msg = f"Unexpected error: {str(e)}"
                print(f"Error: {error_msg}")
                self.logger.error(f"Unexpected error in layer {layer_num} selection: {e}")
                
                if attempts >= max_attempts:
                    raise ConfigurationError(
                        f"Failed to get method selection for layer {layer_num}",
                        parameter=f"layer_{layer_num}_methods",
                        details=str(e)
                    )
                continue
                continue
    
    def _check_combination_count(self, layer_selections: List[List[int]]) -> None:
        """
        Calculate total combination count and warn if exceeds 1000.
        
        Args:
            layer_selections (List[List[int]]): Selected methods for each layer
            
        Requirements: 3.4, 3.5
        
        Raises:
            UserCancellationError: If user declines to continue with high combination count
        """
        try:
            # Calculate combination count
            total_combinations = 1
            for layer_methods in layer_selections:
                total_combinations *= len(layer_methods)
            
            should_warn = total_combinations > 1000
            
            print(f"\n=== 组合摘要 ===")
            print(f"总共需要处理的组合数: {total_combinations}")
            
            if should_warn:
                print("⚠️  警告: 检测到高组合数量!")
                print("   处理超过1000个组合可能需要很长时间")
                print("   并生成大量输出文件。建议减少方法选择。")
                
                max_attempts = 3
                attempts = 0
                
                while attempts < max_attempts:
                    try:
                        confirm = input("是否要继续? (y/n): ").strip().lower()
                        
                        if confirm in ['y', 'yes', '是', '继续']:
                            print("继续使用当前配置...")
                            self.logger.info(f"User confirmed high combination count: {total_combinations}")
                            break
                        elif confirm in ['n', 'no', '否', '取消']:
                            raise UserCancellationError("Configuration cancelled due to high combination count", "combination_count_warning")
                        else:
                            attempts += 1
                            print("请输入 'y' 表示是或 'n' 表示否。")
                            if attempts >= max_attempts:
                                raise UserCancellationError("Too many invalid responses to combination count warning", "combination_count_warning")
                            
                    except KeyboardInterrupt:
                        raise UserCancellationError("Combination count confirmation interrupted by user", "combination_count_warning")
            else:
                print("✓ 组合数量在推荐范围内")
                self.logger.info(f"Combination count approved: {total_combinations}")
                
        except UserCancellationError:
            raise
        except Exception as e:
            self.logger.error(f"Error checking combination count: {e}")
            # Continue without warning if calculation fails
            print("Warning: Could not calculate combination count, proceeding anyway.")
    
    def create_configuration(self, input_file: str, output_directory: str, 
                           available_methods: List[int], method_names: Dict[int, str] = None) -> ProcessingConfiguration:
        """
        Create complete processing configuration through user interaction.
        
        Args:
            input_file (str): Path to input Excel file
            output_directory (str): Directory for output files
            available_methods (List[int]): Available preprocessing method IDs
            
        Returns:
            ProcessingConfiguration: Complete configuration object
            
        Requirements: 2.4
        
        Raises:
            ConfigurationError: If configuration creation fails
            UserCancellationError: If user cancels configuration
        """
        try:
            # Validate inputs
            # 简化验证，只检查基本要求
            if not input_file or not isinstance(input_file, str):
                raise ConfigurationError(
                    "Invalid input file path",
                    parameter="input_file",
                    provided_value=input_file
                )
            
            if not output_directory or not isinstance(output_directory, str):
                raise ConfigurationError(
                    "Invalid output directory path",
                    parameter="output_directory",
                    provided_value=output_directory
                )
            
            # 确保输出目录存在
            import os
            os.makedirs(output_directory, exist_ok=True)
            
            if not available_methods:
                raise ConfigurationError(
                    "No preprocessing methods available",
                    parameter="available_methods"
                )
            
            self.logger.info("Starting configuration creation")
            
            # Get layer count
            layer_count = self.get_layer_count()
            
            # Get method selections for each layer
            layer_selections = self.get_layer_selections(layer_count, available_methods, method_names)
            
            # Create and return configuration
            config = ProcessingConfiguration(
                input_file=input_file,
                layer_count=layer_count,
                layer_selections=layer_selections,
                output_directory=output_directory
            )
            
            print(f"\n✓ 配置完成!")
            print(f"  输入文件: {input_file}")
            print(f"  层数: {layer_count}")
            print(f"  输出目录: {output_directory}")
            
            self.logger.info(f"Configuration created successfully: {layer_count} layers, {len(available_methods)} available methods")
            return config
            
        except (ConfigurationError, UserCancellationError):
            # Re-raise our custom exceptions
            raise
        except Exception as e:
            error = ConfigurationError(
                "Failed to create configuration",
                details=str(e)
            )
            print(f"Error during configuration: {error.get_user_message()}")
            self.logger.error(f"Configuration creation failed: {e}")
            raise error